package com.youka.bussiness.common.utils;

import com.youka.bussiness.common.base.BaseException;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.*;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RsaSecret {

    /**
     * 加密时小于(KEYSIZE / 8) - 11 即可
     */
    public static final int encodeLen = 245;
    /**
     * 定义字节集合
     */
    public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
    /**
     * KEY_ALGORITHM
     */
    public static final String KEY_ALGORITHM = "RSA";
    /**
     * 加密Key的长度等于1024
     */
    public static final int KEYSIZE = 2048;
    /**
     * 解密时必须按照此分组解密
     */
    public static final int decodeLen = KEYSIZE / 8;
    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    /**
     * 加密填充方式
     * android系统的RSA实现是"RSA/None/NoPadding"
     * 而标准JDK实现是"RSA/None/PKCS1Padding",这造成了在android机上加密后无法在服务器上解密的原因
     */
    public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";

    /**
     * 从文件中加载公钥
     *
     * @return
     * @throws Exception 加载公钥异常
     */
    private static String loadPublicKeyByFile() {
        try {
            //创建流
            InputStream in = RsaSecret.class.getResourceAsStream("/cert/publicKey.pem");

            //读取公钥文件
            BufferedReader br = new BufferedReader(new InputStreamReader(in));

            //数据寄存器
            StringBuilder sb = new StringBuilder();

            //定义读取每行的数据
            String readLine;

            //按行读取，读取到空结束
            while ((readLine = br.readLine()) != null) {

                //放入寄存去
                sb.append(readLine);
            }

            //关闭流
            br.close();

            //返回公钥字符串
            return sb.toString();

        } catch (IOException e) {
            throw new BaseException("RSA00001", "公钥数据流读取错误");
        } catch (NullPointerException e) {
            throw new BaseException("RSA00002", "公钥输入流为空");
        }
    }

    /**
     * 从文件中加载私钥
     *
     * @return
     * @throws Exception 加载私钥异常
     */
    private static String loadPrivateKeyByFile() {
        try {
            //创建流
            InputStream in = RsaSecret.class.getResourceAsStream("/cert/privateKey.pem");

            //读取私钥文件
            BufferedReader br = new BufferedReader(new InputStreamReader(in));

            //数据寄存器
            StringBuilder sb = new StringBuilder();

            //定义读取每行的数据
            String readLine;

            //按行读取，读取到空结束
            while ((readLine = br.readLine()) != null) {

                //放入寄存去
                sb.append(readLine);
            }

            //关闭流
            br.close();

            //返回私钥
            return sb.toString();

        } catch (IOException e) {
            throw new BaseException("RSA00001", "公钥数据流读取错误");
        } catch (NullPointerException e) {
            throw new BaseException("RSA00002", "公钥输入流为空");
        }
    }

    /**
     * 从字符串中加载公钥
     *
     * @return
     * @throws Exception
     */
    private static PublicKey loadPublicKey() {
        try {
            //从文件读取公钥,Base64编码
            String publicKeyStr = loadPublicKeyByFile();

            //得到公钥字节数组
            byte[] bytes = Base64.decodeBase64(publicKeyStr);

            //初始化
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);

            //使用RSA
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);

            //返回公钥
            return keyFactory.generatePublic(keySpec);

        } catch (NoSuchAlgorithmException e) {
            throw new BaseException("RSA00003", "无此算法");
        } catch (InvalidKeySpecException e) {
            throw new BaseException("RSA00004", "公钥非法");
        } catch (NullPointerException e) {
            throw new BaseException("RSA00005", "公钥数据为空");
        }
    }

    /**
     * 从字符串中加载私钥
     *
     * @return
     * @throws Exception
     */
    private static PrivateKey loadPrivateKey() {
        try {

            //从文件读取私钥,Base64编码
            String privateKeyStr = loadPrivateKeyByFile();

            //得到私钥字节数组
            byte[] bytes = Base64.decodeBase64(privateKeyStr);

            //初始化
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);

            //使用RSA
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);

            //返回秘钥
            return keyFactory.generatePrivate(keySpec);

        } catch (NoSuchAlgorithmException e) {
            throw new BaseException("RSA00003", "无此算法");
        } catch (InvalidKeySpecException e) {
            throw new BaseException("RSA00004", "公钥非法");
        } catch (NullPointerException e) {
            throw new BaseException("RSA00005", "公钥数据为空");
        }
    }

    /**
     * 通过公钥加密
     *
     * @param sourceData 待加密的数据,字符串
     * @return
     * @throws Exception
     */
    public static String encryptPublicKey(String sourceData) throws Exception {
        //检查待加密数据
        if (sourceData == null) {
            throw new BaseException("RSA00006", "待加密的数据为空,请检查");
        }

        //对待加密的数据字节处理
        byte[] encodeData = sourceData.getBytes();

        //定义加密存储器
        byte[] encode = new byte[]{};

        //分段加密
        for (int i = 0; i < encodeData.length; i += encodeLen) {

            //数据拷贝
            byte[] subArray = subArray(encodeData, i, i + encodeLen);

            //加密
            byte[] doFinal = encryptByPublicKey(subArray);

            //合并
            encode = addAll(encode, doFinal);
        }

        //返回时使用Base64编码
        return Base64.encodeBase64String(encode);
    }

    /**
     * 通过私钥解密
     *
     * @param sourceData 待解密的数据,Base64编码
     * @return
     * @throws Exception
     */
    public static String decryptPrivateKey(String sourceData) throws Exception {
        //检查待解密的数据
        if (sourceData == null) {
            throw new BaseException("RSA00006", "待加密的数据为空,请检查");
        }

        //拿到的带解密数据是经过Base64编码的,所以要先经过Base64解码
        byte[] decodeData = Base64.decodeBase64(sourceData);

        //定义解密存储器
        byte[] decode = new byte[]{};

        //分段解密
        for (int i = 0; i < decodeData.length; i += decodeLen) {

            //数据拷贝
            byte[] subArray = subArray(decodeData, i, i + decodeLen);

            //解密
            byte[] doFinal = decryptByPrivateKey(subArray);

            //合并
            decode = addAll(decode, doFinal);
        }

        //解密后为字节数据所以要字符串化,默认使用UTF-8编码
        return new String(decode, "UTF-8");
    }

    /**
     * 通过私钥加密
     *
     * @param sourceData 待加密的数据,字符串
     * @return
     * @throws Exception
     */
    public static String encryptPrivateKey(String sourceData) {
        try {
            //检查待加密的数据
            if (sourceData == null) {
                throw new BaseException("RSA00006", "待加密的数据为空,请检查");
            }

            //对待加密的数据字节处理
            byte[] encodeData = sourceData.getBytes();

            //定义加密存储器
            byte[] encode = new byte[]{};

            //分段加密
            for (int i = 0; i < encodeData.length; i += encodeLen) {

                //数据拷贝
                byte[] subarray = subArray(encodeData, i, i + encodeLen);

                //加密
                byte[] doFinal = encryptByPrivateKey(subarray);

                //合并
                encode = addAll(encode, doFinal);
            }

            //返回时使用Base64编码
            return Base64.encodeBase64String(encode);
        } catch (Exception e) {
            throw new BaseException("101", "加密失败");
        }
    }

    /**
     * 通过公钥解密
     *
     * @param sourceData 待解密的数据,Base64编码
     * @return
     * @throws Exception
     */
    public static String decryptPublicKey(String sourceData) throws Exception {
        //检查待解密的数据
        if (sourceData == null) {
            throw new BaseException("RSA00006", "待加密的数据为空,请检查");
        }

        //拿到的带解密数据是经过Base64编码的,所以要先经过Base64解码
        byte[] decodeData = Base64.decodeBase64(sourceData);

        //定义解密存储器
        byte[] decode = new byte[]{};

        //分段解密
        for (int i = 0; i < decodeData.length; i += decodeLen) {

            //数据拷贝
            byte[] subarray = subArray(decodeData, i, i + decodeLen);

            //解密
            byte[] doFinal = decryptByPublicKey(subarray);

            //合并
            decode = addAll(decode, doFinal);
        }

        //解密后为字节数据所以要字符串化,默认使用UTF-8编码
        return new String(decode, "UTF-8");
    }

    /**
     * 用公钥加密
     *
     * @param data 待加密的字节数组
     * @return
     * @throws Exception
     */
    private static byte[] encryptByPublicKey(byte[] data) throws Exception {
        //检查待加密数据
        if (data == null) {
            throw new BaseException("RSA00006", "待加密的数据为空,请检查");
        }

        //取得公钥
        Key key = loadPublicKey();

        //获取密码实例
        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);

        //初始化
        cipher.init(Cipher.ENCRYPT_MODE, key);

        //加密返回
        return cipher.doFinal(data);
    }

    /**
     * 用私钥解密
     *
     * @param data 待解密的字节数据
     * @return
     * @throws Exception
     */
    private static byte[] decryptByPrivateKey(byte[] data) throws Exception {
        //检查待解密数据
        if (data == null) {
            throw new BaseException("RSA00006", "待加密的数据为空,请检查");
        }

        //取得私钥
        Key key = loadPrivateKey();

        //获取密码实例
        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);

        //初始化
        cipher.init(Cipher.DECRYPT_MODE, key);

        //解密返回
        return cipher.doFinal(data);
    }

    /**
     * 用私钥加密
     *
     * @param data 待加密的字节数组
     * @return
     * @throws Exception
     */
    private static byte[] encryptByPrivateKey(byte[] data) throws Exception {
        //检查待加密数据
        if (data == null) {
            throw new BaseException("RSA00006", "待加密的数据为空,请检查");
        }

        //取得私钥
        Key key = loadPrivateKey();

        //获取密码实例
        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);

        //初始化
        cipher.init(Cipher.ENCRYPT_MODE, key);

        //加密返回
        return cipher.doFinal(data);
    }

    /**
     * 用公钥解密
     *
     * @param data 带解密字节数组
     * @return
     * @throws Exception
     */
    private static byte[] decryptByPublicKey(byte[] data) throws Exception {
        //检查待解密数据
        if (data == null) {
            throw new BaseException("RSA00006", "待加密的数据为空,请检查");
        }

        //取得公钥
        Key key = loadPublicKey();

        //获取密码实例
        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);

        //初始化
        cipher.init(Cipher.DECRYPT_MODE, key);

        //解密返回
        return cipher.doFinal(data);
    }

    /**
     * 用私钥对信息生成数字签名
     *
     * @param sourceData 已加密数据(BASE64编码)
     * @return
     * @throws Exception
     */
    public static String sign(String sourceData) throws Exception {
        //加载私钥
        PrivateKey key = loadPrivateKey();

        //获取签名实例
        Signature signature = Signature.getInstance("SIGNATURE_ALGORITHM");

        //初始化
        signature.initSign(key);

        //更新签名
        signature.update(Base64.decodeBase64(sourceData));

        //返回签名信息
        return Base64.encodeBase64String(signature.sign());
    }

    /**
     * 用公钥校验数字签名
     *
     * @param sourceData 已加密数据(Base64编码)
     * @param sign       数字签名
     * @return
     * @throws Exception
     */
    public static boolean verify(String sourceData, String sign) throws Exception {
        //加载公钥
        PublicKey key = loadPublicKey();

        //获取签名实例
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);

        //初始化
        signature.initVerify(key);

        //更新签名
        signature.update(Base64.decodeBase64(sourceData));

        //返回延签结果
        return signature.verify(Base64.decodeBase64(sign));
    }

    /**
     * 截取数组
     *
     * @param array               被截取的数组
     * @param startIndexInclusive 开始位置
     * @param endIndexExclusive   结束位置
     * @return
     */
    private static byte[] subArray(final byte[] array, int startIndexInclusive, int endIndexExclusive) {
        if (array == null) {
            return null;
        }
        if (startIndexInclusive < 0) {
            startIndexInclusive = 0;
        }
        if (endIndexExclusive > array.length) {
            endIndexExclusive = array.length;
        }
        final int newSize = endIndexExclusive - startIndexInclusive;
        if (newSize <= 0) {
            return EMPTY_BYTE_ARRAY;
        }

        final byte[] subArray = new byte[newSize];
        System.arraycopy(array, startIndexInclusive, subArray, 0, newSize);
        return subArray;
    }

    /**
     * 数组合并
     *
     * @param target
     * @param source
     * @return
     */
    private static byte[] addAll(final byte[] target, final byte... source) {
        if (target == null) {
            return clone(source);
        } else if (source == null) {
            return clone(target);
        }
        final byte[] joinedArray = new byte[target.length + source.length];
        System.arraycopy(target, 0, joinedArray, 0, target.length);
        System.arraycopy(source, 0, joinedArray, target.length, source.length);
        return joinedArray;
    }

    /**
     * 数组克隆
     *
     * @param array 目标数组
     * @return
     */
    private static byte[] clone(final byte[] array) {
        if (array == null) {
            return null;
        }
        return array.clone();
    }

    /**
     * 随机生成密钥对
     */
    public static void genKeyPair(String filePath) {
        //KeyPairGenerator类用于生成公钥和私钥对，基于RSA算法生成对象
        KeyPairGenerator keyPairGen = null;

        try {
            //使用RSA
            keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);

        } catch (NoSuchAlgorithmException e) {
            throw new BaseException("RSA00008", "获取RSA秘钥实例发生异常");
        }
        //初始化密钥对生成器，密钥大小为96-1024位
        keyPairGen.initialize(KEYSIZE);

        //生成一个密钥对，保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();

        //得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        //得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();

        try {
            //得到公钥字符串
            String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());

            //得到私钥字符串
            String privateKeyString = Base64.encodeBase64String(privateKey.getEncoded());

            //将公钥写入文件
            FileWriter pubfw = new FileWriter(filePath + "/cert/publicKey.pem");

            //将私钥写入文件
            FileWriter prifw = new FileWriter(filePath + "/cert/privateKey.pem");

            //缓存区
            BufferedWriter pubbw = new BufferedWriter(pubfw);
            BufferedWriter pribw = new BufferedWriter(prifw);

            //写入文件
            pubbw.write(publicKeyString);
            pribw.write(privateKeyString);

            //刷新缓冲区
            pubbw.flush();

            //关闭流
            pubbw.close();
            pubfw.close();

            //刷新缓冲区
            pribw.flush();

            //关闭流
            pribw.close();
            prifw.close();
        } catch (Exception e) {
            throw new BaseException("RSA00007", "写入秘钥到文件发生异常");
        }
    }

}